global loader:function  ; making entry point visible to linker
extern kernel_begin_addr, end_of_data, end_of_kernel

%define breakpoint ; xchg bx,bx

section .text

bits 32

loader:

   cli ; We don't want to enable this until we handle our interrupts later on...

   mov esp, stack+STACKSIZE           ; set up the stack

   xor ecx, ecx
   push ecx
   push eax ; Save these for later, they were passed by the boot loader....
   push ecx
   push ebx

   breakpoint

   call capability_detection          ; Fairly useless - this is compiled as
                                      ; an elf64 file; if we are loaded, we
                                      ; should be able to run....

   jmp setup64 ; Note: this cannot be a call, as we would push a 32-bit pointer.
               ; If we tried to return, it should crash as we'll be in 64-bit
               ; mode, expecting a 64-bit pointer.....

hang:
   hlt                                ; halt machine should kernel return
   jmp   hang

; setting up the Multiboot header - see GRUB docs for details
MODULEALIGN equ  1<<0                   ; align loaded modules on page boundaries
MEMINFO     equ  1<<1                   ; provide memory map
VIDEOINFO   equ  1<<2                   ; video information provided
LOAD_KLUDGE equ  1<<16                  ; GRUB fails without these....
FLAGS       equ  MODULEALIGN | MEMINFO | LOAD_KLUDGE | VIDEOINFO  ; this is the Multiboot 'flag' field
MAGIC       equ    0x1BADB002           ; 'magic number' lets bootloader find the header
CHECKSUM    equ 0 -(MAGIC + FLAGS)        ; checksum required

align 4
MultiBootHeader:
   dd MAGIC
   dd FLAGS
   dd CHECKSUM
   dd MultiBootHeader   ; header_addr (flags[16])
   dd kernel_begin_addr ; load_addr (flags[16])
   dd end_of_data       ; load_end_addr (flags[16])
   dd end_of_kernel     ; bss_end_addr (flags[16])
   dd loader            ; entry_addr (flags[16])
   dd 1                 ; mode type (flags[2])
   dd 0                 ; width (flags[2])
   dd 0                 ; height (flags[2])
   dd 0                 ; depth (flags[2])

;
;debug:
;   mov word [esi], 0x2a30
;   add esi, 2
;   ret

;
;

capability_detection:
    pushfd               ; Store the FLAGS-register.
    pop eax              ; Restore the A-register.
    mov ecx, eax         ; Set the C-register to the A-register.
    xor eax, 1 << 21     ; Flip the ID-bit, which is bit 21.
    push eax             ; Store the A-register.
    popfd                ; Restore the FLAGS-register.
    pushfd               ; Store the FLAGS-register.
    pop eax              ; Restore the A-register.
    push ecx             ; Store the C-register.
    popfd                ; Restore the FLAGS-register.
    xor eax, ecx         ; Do a XOR-operation on the A-register and the C-register.
    jz hang              ; The zero flag is set, no CPUID.
    ; CPUID is available for use.

    ; Is extended mode available?
    mov eax, 0x80000000    ; Set the A-register to 0x80000000.
    cpuid                  ; CPU identification.
    cmp eax, 0x80000001    ; Compare the A-register with 0x80000001.
    jb hang                ; It is less, there is no long mode.

    ; And is long mode supported?
    mov eax, 0x80000001    ; Set the A-register to 0x80000001.
    cpuid                  ; CPU identification.
    test edx, 1 << 29      ; Test if the LM-bit, which is bit 29, is set in the D-register.

    jz hang                ; They aren't, there is no long mode.

    ret ; No problems found, continue loading

extern end_of_kernel

;All Intel processors since Pentium Pro (with exception of the Pentium M at 400 Mhz)
; and all AMD since the Athlon series implement the Physical Address Extension (PAE).
; This feature allows you to access up to 64 GB (2^36) of RAM. You can check for this
; feature using CPUID. Once checked, you can activate this feature by setting bit 5
; in CR4. Once active, the CR3 register points to a table of 4 64bit entries, each one
; pointing to a page directory made of 4096 bytes (like in normal paging), divided
; into 512 64bit entries, each pointing to a 4096 byte page table, divided into 512
; 64bit page entries.

make_paging:
    ; Set up basic paging.
    ; This is a basic page table that is only used until the 64-bit version
    ; called from the C++ code can be loaded....
    mov edi, 0x1000    ; Set the destination index to 0x1000.
    mov cr3, edi       ; Set control register 3 to the destination index.
    xor eax, eax       ; Nullify the A-register.
    mov ecx, 4096      ; Set the C-register to 4096.
    rep stosd          ; Clear the memory.
    mov edi, cr3       ; Set the destination index to control register 3.

    mov DWORD [edi], 0x2003      ; Set the double word at the destination index to 0x2003.
    add edi, 0x1000              ; Add 0x1000 to the destination index.
    mov DWORD [edi], 0x3003      ; Set the double word at the destination index to 0x3003.
    add edi, 0x1000              ; Add 0x1000 to the destination index.
    mov DWORD [edi], 0x4003      ; Set the double word at the destination index to 0x4003.
    add edi, 0x1000              ; Add 0x1000 to the destination index.

    mov ebx, 0x00000003          ; Set the B-register to 0x00000003.
    mov ecx, 512                 ; Set the C-register to 512.

.SetEntry:
    mov DWORD [edi], ebx         ; Set the double word at the destination index to the B-register.
    add ebx, 0x1000              ; Add 0x1000 to the B-register.
    add edi, 8                   ; Add eight to the destination index.
    loop .SetEntry               ; Set the next entry.

    ret
;    mov eax, 0x1001       ; PDPT
;    mov dword [pdpt], eax ; 0001_b indicates present
;
;    mov eax, 0x2003         ; PD - Two for first 4 Mb
;    mov dword [0x1000], eax ; 0011_b indicates present and r/w
;    mov dword [0x1004], 0
;    mov eax, 0x3003
;    mov dword [0x1008], eax
;    mov dword [0x100C], 0
;
;    mov edi, 0x1010 ; Zero out the rest of the directory
;    xor eax, eax    ; if the first bit is zero, the table it
;    mov ecx, 4080   ; references is not present in memory
;    rep stosd       ; Careful: if we reference these, it generates an interrupt
;
;    mov edi, 0x1000     ; PT - two sets of complete tables -> 4 Mb
;    mov ebx, 0x00000003 ; flags -> indicates present and r/w
;    mov ecx, 1024       ; two tables of 512 each
;.loop:
;    mov dword [edi], ebx
;    add ebx, 0x1000      ; point to next page
;    add edi, 8           ; entries are 8 bytes long
;    loop .loop
;
;    ret

setup64:
    mov eax, cr0 ; 1. Disable paging, 32nd bit of cr0
    and eax, 0x7FFFFFFF
    mov cr0, eax

    mov eax, cr4   ; 2. Enable PAE, 6th bit of cr4
    or eax, 100000b
    mov cr4, eax

    call make_paging ; 3a. Prepare paging

    mov eax, 0x1000 ; 3. Load cr3 with the physical address of the page table
    mov cr3, eax

    mov ecx, 0xC0000080 ; 4. Enable IA-32e mode by setting IA32_EFER.LME = 1.
    rdmsr
    or eax, 0x00000100
    wrmsr

    mov eax, cr0 ; 5. Enable paging, 31st bit of cr0
    or eax, 0x80000000
    mov cr0, eax

    ; The change from compatibility to 64-bit mode, we need a fresh jump
    ; using a 64-bit GDT pointer

    lgdt [GDT64.Pointer]         ; Load the 64-bit global descriptor table.
;    jmp Realm64
    jmp CodeSeg:Realm64 ; Set the code segment and enter 64-bit long mode.; Use 64-bit.

bits 64

extern boot             ; boot is defined elsewhere

Realm64:
    cli                           ; Clear the interrupt flag.
    mov ax, DataSeg               ; Set the A-register to the data descriptor.
    mov ss, ax
    mov ds, ax                    ; Set the data segment to the A-register.
    mov es, ax                    ; Set the extra segment to the A-register.
    mov fs, ax                    ; Set the F-segment to the A-register.
    mov gs, ax                    ; Set the G-segment to the A-register.
    mov edi, 0xB8000              ; Set the destination index to 0xB8000.
    mov rax, 0x1F201F201F201F20   ; Set the A-register to 0x1F201F201F201F20.
    mov ecx, 500                  ; Set the C-register to 500.
    rep movsq                     ; Clear the screen.

    breakpoint

;    ltr TSSSeg

    call boot ; Call the kernel proper

align 32

pdpt:

times 4 dq 0

global NullSeg, CodeSeg, DataSeg, DPL1CodeSeg, DPL1DataSeg

GDT64:                          ; Global Descriptor Table (64-bit).
    NullSeg: equ $ - GDT64      ; The null descriptor.
    dq 0                        ;
    CodeSeg: equ $ - GDT64      ; The code descriptor.
    dw 0                        ; Limit (low)
    dw 0                        ; Base (low)
    db 0                        ; Base (middle)
    db 10011000b                ; P | DPL | S | Type (Code, Execute-Only)
    db 00100000b                ; G | D/B | L | Avl | Limit (high)
    db 0                        ; Base (high)
    DataSeg: equ $ - GDT64      ; The data descriptor.
    dw 0                        ; Limit (low)
    dw 0                        ; Base (low)
    db 0                        ; Base (middle)
    db 10010010b                ; P | DPL | S | Type (Data, Read/Write)
    db 00100000b                ; G | D/B | L | Avl | Limit (high)
    db 0                        ; Base (high)
    DPL1CodeSeg: equ $ - GDT64  ; The dpl 1 code descriptor.
    dw 0                        ; Limit (low)
    dw 0                        ; Base (low)
    db 0                        ; Base (middle)
    db 10111000b                ; P | DPL | S | Type (Code, Execute-Only)
    db 00100000b                ; G | D/B | L | Avl | Limit (high)
    db 0                        ; Base (high)
    DPL1DataSeg: equ $ - GDT64  ; The dpl 1 data descriptor.
    dw 0                        ; Limit (low)
    dw 0                        ; Base (low)
    db 0                        ; Base (middle)
    db 10110010b                ; P | DPL | S | Type (Data, Read/Write)
    db 00100000b                ; G | D/B | L | Avl | Limit (high)
    db 0                        ; Base (high)
    DPL2CodeSeg: equ $ - GDT64  ; The dpl 2 code descriptor.
    dw 0                        ; Limit (low)
    dw 0                        ; Base (low)
    db 0                        ; Base (middle)
    db 11011000b                ; P | DPL | S | Type (Code, Execute-Only)
    db 00100000b                ; G | D/B | L | Avl | Limit (high)
    db 0                        ; Base (high)
    DPL2DataSeg: equ $ - GDT64  ; The dpl 2 data descriptor.
    dw 0                        ; Limit (low)
    dw 0                        ; Base (low)
    db 0                        ; Base (middle)
    db 11010010b                ; P | DPL | S | Type (Data, Read/Write)
    db 00100000b                ; G | D/B | L | Avl | Limit (high)
    db 0                        ; Base (high)
    DPL3CodeSeg: equ $ - GDT64  ; The dpl 3 code descriptor.
    dw 0                        ; Limit (low)
    dw 0                        ; Base (low)
    db 0                        ; Base (middle)
    db 11111000b                ; P | DPL | S | Type (Code, Execute-Only)
    db 00100000b                ; G | D/B | L | Avl | Limit (high)
    db 0                        ; Base (high)
    DPL3DataSeg: equ $ - GDT64  ; The dpl 3 data descriptor.
    dw 0                        ; Limit (low)
    dw 0                        ; Base (low)
    db 0                        ; Base (middle)
    db 11110010b                ; P | DPL | S | Type (Data, Read/Write)
    db 00100000b                ; G | D/B | L | Avl | Limit (high)
    db 0                        ; Base (high)
;    TSSSeg: equ $ - GDT64       ; Used for the TSS
;    dw (TSS.end - TSS) & 0xFFFF ; Limit
;    dw (TSS & 0xFFFF)
;    db ((TSS >> 16) & 0xFF)
;    db 0x89
;    db (((TSS.end - TSS) >> 16) & 0xF) | 0x80
;    dq ((TSS >> 24) & 0xFFFFFFFFFF)
;    db 0
;    dw 0
    .Pointer:                   ; The GDT-pointer.
    dw $ - GDT64 - 1            ; Limit.
    dq GDT64                    ; Base.

TSS:
    dd 0 ; Reserved
    dq (endstack - 0x100)
    dq 0
    dq 0
    dq 0 ; Reserved
    times 7 dq 0
    dq 0 ; Reserved
    dw 0 ; Reserved
    dw (IOMap - TSS)
.end:

IOMap:
    times 32 db 0
    db 0xFF

; reserve initial kernel stack space
STACKSIZE equ 0x4000                  ; that's 16kb.

section .bss
align 4

stack:
   resb STACKSIZE                     ; reserve 16k stack on a doubleword boundary

endstack:
